﻿#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "calibrator.h"
#include "camerawindow.h"
#include <QString>
#include <QFileDialog>
#include <QTextCodec>
#include <string>
#include <QThread>
#include <QDebug>
#include <QInputDialog>
#include <QMessageBox>
#include <QFile>
#include <QMetaEnum>
#include <QHash>
#include <QKeyEvent>
#include <QDir>
#include <QDirIterator>
#include <string>
#include <deque>

using namespace std;

MainWindow::MainWindow(int argc, char** argv, QWidget *parent)
    : m_argc(argc), m_argv(argv), QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    qDebug() << "窗体线程" << QThread::currentThreadId();

    Calibrator* p_caltor = new Calibrator(m_argc, m_argv);
    QThread* p_calThread = new QThread();
    p_caltor->moveToThread(p_calThread);

    // 标定
    connect(ui->btn_Cal, &QPushButton::clicked, p_caltor, Calibrator::calMainRun);
    // 矫正图像
    connect(ui->btn_DoCal, &QPushButton::clicked, this, &MainWindow::selectImgAndUndistort);
    connect(this, &MainWindow::sg_DoCal, p_caltor, &Calibrator::doCal);
    // 窗体关闭后销毁线程和标定对象
    connect(this, &MainWindow::destroyed, p_calThread, &QThread::quit);
    connect(p_calThread, &QThread::finished, p_caltor, Calibrator::deleteLater);
    connect(p_calThread, &QThread::finished, p_calThread, QThread::deleteLater);
    // 按下新建配置按钮，弹出对话框，输入新配置名称，创建对应文件夹
    connect(ui->btn_NewSet, &QPushButton::clicked, this, &MainWindow::createNewSetting);
    // 选择的配置文件改变，读入对应的配置信息
    connect(ui->cbox_Sets, &QComboBox::currentTextChanged, this, &MainWindow::setFileChanged);
    // 配置改变，保存当前信息
    connect(ui->txb_BoardH, &QLineEdit::textChanged, this, &MainWindow::txbTextChanged);
    connect(ui->txb_BoardW, &QLineEdit::textChanged, this, &MainWindow::txbTextChanged);
    connect(ui->txb_FixAspRatio, &QLineEdit::textChanged, this, &MainWindow::txbTextChanged);
    connect(ui->txb_NrOfFrame, &QLineEdit::textChanged, this, &MainWindow::txbTextChanged);
    connect(ui->txb_SquareSize, &QLineEdit::textChanged, this, &MainWindow::txbTextChanged);
    connect(ui->cbox_CalPattern, &QComboBox::currentTextChanged, this, &MainWindow::cboxTextChanged);
    connect(ui->ckbox_CalibZeroTangentDist, &QCheckBox::stateChanged, this, &MainWindow::ckboxStateChanged);
    connect(ui->ckbox_FixPrincipalPoint, &QCheckBox::stateChanged, this, &MainWindow::ckboxStateChanged);
    connect(ui->ckbox_FlipVertical, &QCheckBox::stateChanged, this, &MainWindow::ckboxStateChanged);
    connect(ui->ckbox_ShowUndistorsed, &QCheckBox::stateChanged, this, &MainWindow::ckboxStateChanged);
    connect(ui->ckbox_WriteExtrinsics, &QCheckBox::stateChanged, this, &MainWindow::ckboxStateChanged);
    connect(ui->ckbox_WriteGrid, &QCheckBox::stateChanged, this, &MainWindow::ckboxStateChanged);
    connect(ui->ckbox_WritePoints, &QCheckBox::stateChanged, this, &MainWindow::ckboxStateChanged);
    // 采集标定图像
    connect(ui->btn_AcqImg, &QPushButton::clicked, this, &MainWindow::acquireImgs);
    // 限制控件输入，安装事件过滤器
    ui->txb_BoardH->installEventFilter(this);
    ui->txb_BoardW->installEventFilter(this);
    ui->txb_FixAspRatio->installEventFilter(this);
    ui->txb_NrOfFrame->installEventFilter(this);
    ui->txb_SquareSize->installEventFilter(this);
    // 禁用中文输入法
    ui->txb_BoardH->setAttribute(Qt::WA_InputMethodEnabled, false);
    ui->txb_BoardW->setAttribute(Qt::WA_InputMethodEnabled, false);
    ui->txb_FixAspRatio->setAttribute(Qt::WA_InputMethodEnabled, false);
    ui->txb_NrOfFrame->setAttribute(Qt::WA_InputMethodEnabled, false);
    ui->txb_SquareSize->setAttribute(Qt::WA_InputMethodEnabled, false);
    // 禁止更改窗体尺寸
    setWindowFlags(windowFlags()&~Qt::WindowMaximizeButtonHint);    // 禁止最大化按钮
    setFixedSize(this->width(), this->height());                    // 禁止拖动窗口大小

    this->initWin();
    p_calThread->start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::initWin() {
    // 把图像窗口的边框去除，文字清除
    ui->lab_ImgWindow->setFrameShape(QFrame::NoFrame);
    ui->lab_ImgWindow->setText("");

    // 检查配置文件目录是否存在，若不存在则新建
    QDir dir;
    if (!dir.exists(m_settingPath)) {
        bool res = dir.mkpath(m_settingPath);
        qDebug() << "新建目录是否成功" << res;
    }

    // 读取配置文件目录到窗体中
    dir = QDir(m_settingPath);
    QFileInfoList fileList = dir.entryInfoList(QStringList());
    for (QFileInfo& file : fileList) {
        QString name = file.fileName();
        // 跳过隐藏的系统文件夹
        if (name == "." || name == "..") {
            continue;
        }
        // 删除非目录文件
        if (!file.isDir()) {
            QFile qfile(file.filePath());
            qfile.remove();
            continue;
        }
        ui->cbox_Sets->addItem(name);
    }

    // 写入标定板类型
    ui->cbox_CalPattern->addItem("CHESSBOARD");
    ui->cbox_CalPattern->addItem("CIRCLES_GRID");
    ui->cbox_CalPattern->addItem("ASYMMETRIC_CIRCLES_GRID");

    // 设置图像显示控件的图像数量上限
    ui->lab_ImgWindow->setCapcity(100);

    // 默认本地采集
    ui->rbt_UseFile->setChecked(true);
}

void MainWindow::selectImgAndUndistort() {
    QString qstring = QFileDialog::getOpenFileName(this, tr("选择图片"), "E:/Desktop", "*.png *.jpg *.bmp *.tif");
    QTextCodec* code = QTextCodec::codecForName("GB2312");
    string path = code->fromUnicode(qstring).data();
    emit this->sg_DoCal(path);
}

void MainWindow::createNewSetting() {
    // 打开对话框，接收用户输入的新文件名
    QString dlgTitle = "新建配置文件";
    QString txtLabel = "请输入文件名";
    QString defaultInput = "";
    QLineEdit::EchoMode echoMode = QLineEdit::Normal;  // 正常文字输入
    //QLineEdit::EchoMode echoMode = QLineEdit::Password;  // 密码输入
    bool ok = false;
    QInputDialog inputDialog;
    QString setName;
    while (true) {
        setName = inputDialog.getText(this, dlgTitle, txtLabel, echoMode, defaultInput, &ok);
        if (ok && !setName.isEmpty() && ui->cbox_Sets->findText(setName) == -1) {
            ui->cbox_Sets->addItem(setName);
            ui->cbox_Sets->setCurrentText(setName);
            break;
        }
        else if (!ok) {
            break;
        }
        else {
            QMessageBox::warning(this, "注意", "文件名不可为空，也不可重复！");
            continue;
        }
    }
    if (!ok) return;
    // 在可执行文件的根目录下创建文件夹
    QDir dir;
    QString newFilePath = m_settingPath + "/" + setName;
    QString imgPath = newFilePath + "/imgs";
    dir.mkpath(newFilePath);
    dir.mkpath(imgPath);
    // 创建xml文件
    Settings set;
    set.m_SettingName = setName;
    set.m_Input = imgPath.toStdString() + "/Imgs.xml";
    set.m_OutputFileName = newFilePath.toStdString() + "/out_camera_data.xml";
    FileStorage fs(newFilePath.toStdString() + "/Settings.xml", FileStorage::WRITE );
    set.write(fs);
    fs.release();
}

void MainWindow::setFileChanged() {
    // 把xml中的参数读入到m_CurrentSet中
    QString name = ui->cbox_Sets->currentText();
    QString setPath = m_settingPath + "/" + name + "/Settings.xml";
    FileStorage fs(setPath.toStdString(), FileStorage::READ);
    fs["Settings"] >> m_CurrentSet;
    fs.release();
    m_CurrentSet.m_SettingName = name;
    // 刷新界面
    this->readSetToUi(m_CurrentSet);
}

void MainWindow::readSetToUi(const Settings& set) {
    // 读取配置参数
    int index = ui->cbox_CalPattern->findText(m_strMap.value(set.m_CalibrationPattern));
    ui->cbox_CalPattern->setCurrentIndex(index);
    ui->txb_BoardH->setText(QString::number(set.m_BoardSize.height));
    ui->txb_BoardW->setText(QString::number(set.m_BoardSize.width));
    ui->txb_FixAspRatio->setText(QString::number(set.m_AspectRatio));
    ui->txb_NrOfFrame->setText(QString::number(set.m_NrFrames));
    ui->txb_SquareSize->setText(QString::number(set.m_SquareSize));
    ui->ckbox_CalibZeroTangentDist->setCheckState(getCkSt(set.m_CalibZeroTangentDist));
    ui->ckbox_FixPrincipalPoint->setCheckState(getCkSt(set.m_CalibFixPrincipalPoint));
    ui->ckbox_FlipVertical->setCheckState(getCkSt(set.m_FlipVertical));
    ui->ckbox_ShowUndistorsed->setCheckState(getCkSt(set.m_ShowUndistorsed));
    ui->ckbox_WriteExtrinsics->setCheckState(getCkSt(set.m_WriteExtrinsics));
    ui->ckbox_WriteGrid->setCheckState(getCkSt(set.m_WriteGrid));
    ui->ckbox_WritePoints->setCheckState(getCkSt(set.m_WritePoints));
    // 读取标定图像
    ui->lab_ImgWindow->clearImgs();
    deque<Mat> imgQue = m_CurrentSet.getImgList();
    if (!imgQue.empty()) {
        ui->lab_ImgWindow->addImgs(m_CurrentSet.getImgList());
        ui->lab_ImgWindow->showImg(0);
    }
}

void MainWindow::txbTextChanged(const QString& str) {
    qDebug() << str;
    QLineEdit* txb = qobject_cast<QLineEdit*>(QObject::sender());
    if (txb->hasFocus()) {
        this->writeSetFromUi();
    }
}

void MainWindow::cboxTextChanged(const QString& str) {
    qDebug() << str;
    QComboBox* cbox = qobject_cast<QComboBox*>(QObject::sender());
    if (cbox->hasFocus()) {
        this->writeSetFromUi();
    }
}

void MainWindow::ckboxStateChanged(int i) {
    qDebug() << i;
    QCheckBox* ckbox = qobject_cast<QCheckBox*>(QObject::sender());
    if (ckbox->hasFocus()) {
        this->writeSetFromUi();
    }
}

void MainWindow::writeSetFromUi() {
    m_CurrentSet.m_CalibrationPattern = m_enumMap.value(ui->cbox_CalPattern->currentText());
    m_CurrentSet.m_BoardSize.height = ui->txb_BoardH->text().toInt();
    m_CurrentSet.m_BoardSize.width = ui->txb_BoardW->text().toInt();
    m_CurrentSet.m_AspectRatio = ui->txb_FixAspRatio->text().toFloat();
    m_CurrentSet.m_NrFrames = ui->txb_NrOfFrame->text().toInt();
    m_CurrentSet.m_SquareSize = ui->txb_SquareSize->text().toInt();
    m_CurrentSet.m_CalibZeroTangentDist = getBool(ui->ckbox_CalibZeroTangentDist->checkState());
    m_CurrentSet.m_CalibFixPrincipalPoint = getBool(ui->ckbox_FixPrincipalPoint->checkState());
    m_CurrentSet.m_FlipVertical = getBool(ui->ckbox_FlipVertical->checkState());
    m_CurrentSet.m_ShowUndistorsed = getBool(ui->ckbox_ShowUndistorsed->checkState());
    m_CurrentSet.m_WriteExtrinsics = getBool(ui->ckbox_WriteExtrinsics->checkState());
    m_CurrentSet.m_WriteGrid = getBool(ui->ckbox_WriteGrid->checkState());
    m_CurrentSet.m_WritePoints = getBool(ui->ckbox_WritePoints->checkState());
    QString path = m_settingPath + "/" + m_CurrentSet.m_SettingName + "/Settings.xml";
    FileStorage fs(path.toStdString(), FileStorage::WRITE);
    m_CurrentSet.write(fs);
    fs.release();
}


bool MainWindow::eventFilter(QObject* watched, QEvent* e) {
    // 限制按键输入，只能输入数字和小数点以及删除键
    if (e->type() == QEvent::KeyPress &&
            (watched == ui->txb_BoardH || watched == ui->txb_BoardW || watched == ui->txb_FixAspRatio ||
             watched == ui->txb_NrOfFrame || watched == ui->txb_SquareSize)) {
        QLineEdit* txb = static_cast<QLineEdit*> (watched);
        QKeyEvent* event = static_cast<QKeyEvent*> (e);
        int k = event->key();
        if (k != Qt::Key_0 && k != Qt::Key_1 && k != Qt::Key_2 &&
                k != Qt::Key_3 && k != Qt::Key_4 && k != Qt::Key_5 &&
                k != Qt::Key_6 && k != Qt::Key_7 && k != Qt::Key_8 &&
                k != Qt::Key_9 && k != Qt::Key_Period && k != Qt::Key_Backspace) {
            return true;
        }
        else if (k == Qt::Key_Period) {
            // 只有txb_FixAspRatio可以输入小数点
            if (watched != ui->txb_FixAspRatio) {
                return true;
            }
            // 小数点不能是第一位，不能超过2个
            if (txb->cursorPosition() == 0) {
                return true;
            }
            if (txb->text().indexOf('.') != -1) {
                return true;
            }
        }
    }
    return QWidget::eventFilter(watched, e);
}

void MainWindow::acquireImgs() {
    vector<string> imageList;
    deque<Mat> imageQue;
    if (ui->rbt_UseFile->isChecked()) {  // 本地读取
        QStringList imgPaths = QFileDialog::getOpenFileNames(this, tr("选择图片"), m_settingPath, "*.png *.jpg *.bmp *.tif");
        if (imgPaths.empty()) {
            return;
        }
        // 清除imgs目录
        QString imgDirPath = m_settingPath + "/" + m_CurrentSet.m_SettingName + "/imgs";
        QDir dir(imgDirPath);
        QDirIterator dirsIterator(imgDirPath, QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot, QDirIterator::NoIteratorFlags);
        while (dirsIterator.hasNext()) {
            if (!dir.remove(dirsIterator.next()))                  // 删除文件操作如果返回否，那它就是目录
            {
                QDir(dirsIterator.filePath()).removeRecursively(); // 删除目录本身以及它下属所有的文件及目录
            }
        }
        // 把每幅图像拷贝到setting目录下，并把新路径存入到imageList下
        QTextCodec* code = QTextCodec::codecForName("GB2312");     //解决中文路径问题
        for (QString str : imgPaths) {
            QStringList pathList = str.split("/");
            QString newPath = imgDirPath + "/" + pathList.back();
            QFile imgFile(str);
            imgFile.copy(newPath);
            imageList.push_back(code->fromUnicode(newPath).data());
            imageQue.push_back(imread(code->fromUnicode(newPath).data()));
        }
    }
    else {  // 相机采集
        CameraWindow* camWin = new CameraWindow(this);
        camWin->setWindowModality(Qt::WindowModal);   // 模态显示窗口
        camWin->show();
    }
    // 将图像显示在ui上
    ui->lab_ImgWindow->clearImgs();
    ui->lab_ImgWindow->addImgs(imageQue);
    ui->lab_ImgWindow->showImg(0);
    // 路径写入到xml文件里
    FileStorage fs(m_CurrentSet.m_Input, FileStorage::WRITE);
    fs << "images" << "[";
    for (const string& str : imageList) {
        fs << str;
    }
    fs << "]";
    // 刷新settings里的imagelist
    m_CurrentSet.m_ImageList = imageList;
}
